﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Diagnostics;
using System.Reflection;
using System.Drawing;
using System.Threading;
using System.Security;

#if WPF
using Silverlight.Hosting.Wpf.Interop;
using EventArgs = System.Windows.RoutedEventArgs;

namespace Silverlight.Hosting.Wpf.Private
#else
using Silverlight.Hosting.Interop;

namespace Silverlight.Hosting.Private
#endif
{
    public abstract class XcpWindowlessContainer : XcpContainer, IOleInPlaceSiteWindowless, IOleInPlaceFrame, IOleInPlaceUIWindow
    {
        private const int BITS_PER_PIXEL = 4;
        private bool _controlSizeDirty;
        private RenderTargetBitmap _renderTargetBitmap;
        private IntPtr _bitsBuffer;
        private SilverlightViewer _silverlightViewer;
        private System.Windows.Forms.Timer _renderTimer;

        public XcpWindowlessContainer() : base(true)
        {
        }

        #region Public Events

        public event EventHandler<FrameRenderedEventArgs> FrameRendered;

        #endregion

        #region Public Methods

        /// <summary>
        /// Sends a PAINT message, forcing silverlight to continue rendering
        /// </summary>
        public void Render()
        {
            if (base.Handle != IntPtr.Zero)
            {
                SendMessage(base.Handle, (uint)WindowMessages.WM_PAINT, IntPtr.Zero, IntPtr.Zero);
            }
        }

        #endregion

        #region Protected Methods

        protected virtual void OnFrameRendered(FrameRenderedEventArgs e)
        {
            if (FrameRendered != null)
            {
                FrameRendered(this, e);
            }
        }
        
        protected override object GetOcx()
        {
            return (base._unkControl ?? (base._unkControl = new XcpControl()));
        }

        protected override void OnResize(System.EventArgs e)
        {
            base.OnResize(e);

            _controlSizeDirty = true;

            if (base._unkControl == null) return;

            var rect = RECT.FromRectangle(ClientRectangle);
            var oleObject = base._unkControl as IOleInPlaceObject;

            /* Inform the windowless control that the size has changed */
            if (oleObject != null)
            {
                oleObject.SetObjectRects(ref rect, ref rect);
            }
        }

        protected override void OnGotFocus(System.EventArgs e)
        {
            base.OnGotFocus(e);

            UIActivate();
        }

        protected override void OnLostFocus(System.EventArgs e)
        {
            base.OnLostFocus(e);

            UIDeactivate();
        }

        protected override void OnHandleCreated(System.EventArgs e)
        {
            base.OnHandleCreated(e);

            if (base.IsHandleCreated)
            {
                if (_unkControl is IOleObject)
                {
                    IOleObject oleObject = (IOleObject)_unkControl;

                    RECT rect = new RECT();
                    MSG msg = new MSG();

                    int hr = oleObject.DoVerb((int)OLEDOVERB.INPLACEACTIVATE, ref msg, this, 0, base.Handle, ref rect);
                    if (hr < 0)
                    {
                        throw new Exception("Could not inplace activate the Silverlight control", Marshal.GetExceptionForHR(hr));
                    }
                }

                _controlSizeDirty = true;

                /* Ensure our render timer has been configured */
                EnsureRenderTimer();

                /* Start rendering */
                _renderTimer.Start();
            }
        }

        protected override void OnHandleDestroyed(System.EventArgs e)
        {
            base.OnHandleDestroyed(e);

            if (_renderTimer != null)
            {
                _renderTimer.Stop();
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            /* Do nothing.  Don't call base OnPaint.  It shouldn't be needed */
            e.Dispose();
        }


        protected internal bool DispatchMessage(ref Message m)
        {
            int handled = HRESULT.S_FALSE;

            if (base._unkControl != null)
            {
                var windowlessInPlace = base._unkControl as IOleInPlaceObjectWindowless;

                if (windowlessInPlace != null)
                {
                    int result = 0;
                    /* Pump these messages to our windowless control */
                    handled = windowlessInPlace.OnWindowMessage((uint)m.Msg, m.WParam, m.LParam, out result);
                }
            }

            return (handled == HRESULT.S_OK);
        }

        protected internal int ProcessInput(uint msg, uint wParam, uint lParam, ref IntPtr curEvent, int x, int y, out uint fHandled)
        {
            int hr = HRESULT.E_FAIL;
            fHandled = 0;

            if (_silverlightViewer != null)
            {
                hr = _silverlightViewer.ProcessInput(msg, wParam, lParam, ref curEvent, x, y, out fHandled);
            }

            return hr;
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == (int)WindowMessages.WM_PAINT)
            {
                if (Width > 0 && Height > 0)
                {
                    RenderSilverlight();
                }
            }
            else
            {
                base.WndProc(ref m);
            }
        }
        
        protected override void Dispose(bool disposing)
        {
            if (_renderTimer != null)
            {
                _renderTimer.Stop();
                _renderTimer = null;
            }

            ReleaseAllResources();

            base.Dispose(disposing);
        }
        
        #endregion

        #region Internal Methods

        internal void UIActivate()
        {
            if (base._unkControl != null)
            {
                var oleObject = base._unkControl as IOleObject;

                if (oleObject == null) return;

                var msg = new MSG();
                var rect = RECT.FromRectangle(ClientRectangle);
                oleObject.DoVerb((int)OLEDOVERB.UIACTIVATE, ref msg, this, 0, Handle, ref rect);
            }
        }

        internal void UIDeactivate()
        {
            if (base._unkControl != null)
            {
                var oleObject = base._unkControl as IOleInPlaceObject;
                if (oleObject != null)
                {
                    oleObject.UIDeactivate();
                }
            }
        }

        #endregion

        #region Private Methods

        [SuppressUnmanagedCodeSecurity]
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        private void EnsureRenderTimer()
        {
            if (_renderTimer == null)
            {
                _renderTimer = new System.Windows.Forms.Timer();

                int maxFrameRate;
                try
                {
                    maxFrameRate = base.Settings.MaxFrameRate;
                }
                catch
                {
                    maxFrameRate = 60;
                }
                _renderTimer.Interval = 1000 / maxFrameRate;

                base.Settings.MaxFrameRateChanged += (s, e) =>
                    {
                        _renderTimer.Interval = 1000 / e.NewValue;
                        _renderTimer.Start();
                    };
                _renderTimer.Tick += (s, e) => { Render(); };
            }
        }

        /// <summary>
        /// Releases all unmanaged resources
        /// </summary>
        private void ReleaseAllResources()
        {
            if (_silverlightViewer != null)
            {
                _silverlightViewer.Release();
                _silverlightViewer = null;
            }

            ReleaseRenderResources();

            if (base._unkControl != null)
            {
                Marshal.ReleaseComObject(base._unkControl);
                base._unkControl = null;
            }
        }

        /// <summary>
        /// Renders the silverlight root element to a buffer
        /// </summary>
        private void RenderSilverlight()
        {
            try
            {
                /* Make sure we have a buffer to render to */
                EnsureRenderBitmap();
            }
            catch
            {
                return;
            }

            if (_unkControl != null)
            {
                XRECT dirtyRect = new XRECT();

                int hr = 0;
                using (var content = base.GetXcpControlContent())
                {
                    var t = content.Object.GetType();
                    try
                    {
                        var root = t.InvokeMember("Root", BindingFlags.GetProperty, null, content.Object, null);

                        /* Get the pointer of the returned COM instance*/
                        using (var pElement = new SafeComHandle(Marshal.GetIUnknownForObject(root)))
                        {
                            /* Render the Silverlight root element to our bitmap */
                            hr = _renderTargetBitmap.Render(pElement.DangerousGetHandle(), out dirtyRect);
                        }
                    }
                    catch (COMException comException)
                    {
                        hr = comException.ErrorCode;
                    }
                }

                if (hr < 0) return;

                if (dirtyRect.Width > 0 && dirtyRect.Height > 0)
                {
                    int bufferSize = Width * Height * BITS_PER_PIXEL;

                    /* Invoke our new frame event */
                    OnFrameRendered(new FrameRenderedEventArgs()
                        {
                            Buffer = _bitsBuffer,
                            BufferSize = bufferSize,
                            Width = Width,
                            Height = Height,
                            DirtyRect = dirtyRect.ToRectangle()
                        });
                }
            }
        }

        /// <summary>
        /// Ensures there is a bitmap to render to.
        /// </summary>
        private void EnsureRenderBitmap()
        {
            if (!_controlSizeDirty) return;

            /* Free any old bitmap we may of had */
            ReleaseRenderResources();

            /* The SilverlightViewer interface allows us
             * to create new render target bitmaps */
            if (_silverlightViewer == null)
            {
                using (var content = base.GetXcpControlContent())
                {
                    ISilverlightViewerProvider viewerProvider = content.Object as ISilverlightViewerProvider;
                    if (viewerProvider == null)
                    {
                        throw new Exception("Could not query for the ISilverlightViewerProvider interface");
                    }

                    IntPtr pViewer = IntPtr.Zero;
                    viewerProvider.GetSilverlightViewer(ref pViewer);

                    /* SilverlightViewer was NOT built as a COM interface,
                     * so we must use our wrapper class for it */
                    _silverlightViewer = new SilverlightViewer(pViewer);
                }
            }

            IntPtr pRtb = IntPtr.Zero;

            /* Allocate us some unmanaged mems */
            _bitsBuffer = Marshal.AllocCoTaskMem(Width * Height * BITS_PER_PIXEL);

            int background;
            try
            {
                var c = this.Settings.Background;
                background = (c.A << 24) | (c.R << 16) | (c.G << 8) | (c.B);
            }
            catch
            {
                background = 0;
            }
            int hr = _silverlightViewer.CreateRenderTargetBitmap((uint)background, 0, 0, (uint)Width, (uint)Height, _bitsBuffer, out pRtb);

            if (hr < 0)
            {
                ReleaseRenderResources();
                throw new Exception("Could not create Silverlight render target");
            }

            /* Render target bitmap was NOT implemented as a COM interface,
             * so we use our custom wrapper class for it */
            _renderTargetBitmap = new RenderTargetBitmap(pRtb);

            _controlSizeDirty = false;
        }

        /// <summary>
        /// Releases our unmanaged resources for rendering
        /// </summary>
        private void ReleaseRenderResources()
        {
            if (_renderTargetBitmap != null)
            {
                _renderTargetBitmap.Release();
                _renderTargetBitmap = null;
            }

            if (_bitsBuffer != IntPtr.Zero)
            {
                Marshal.FreeCoTaskMem(_bitsBuffer);
                _bitsBuffer = IntPtr.Zero;
            }
        }

        #endregion

        #region IOleInPlaceSiteWindowless Members

        int IOleInPlaceSiteWindowless.GetWindow(out IntPtr phwnd)
        {
            phwnd = Handle;

            if (phwnd == IntPtr.Zero)
                return HRESULT.E_FAIL;
            else
                return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.ContextSensitiveHelp(int fEnterMode)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceSiteWindowless.CanInPlaceActivate()
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.OnInPlaceActivate()
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.OnUIActivate()
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.GetWindowContext(out IOleInPlaceFrame ppFrame, out IOleInPlaceUIWindow ppDoc, RECT[] lprcPosRect, RECT[] lprcClipRect, OLEINPLACEFRAMEINFO[] lpFrameInfo)
        {
            ppDoc = this;
            ppFrame = this;

            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.Scroll(SIZE scrollExtant)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceSiteWindowless.OnUIDeactivate(int fUndoable)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.OnInPlaceDeactivate()
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.DiscardUndoState()
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceSiteWindowless.DeactivateAndUndo()
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceSiteWindowless.OnPosRectChange(RECT[] lprcPosRect)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceSiteWindowless.OnInPlaceActivateEx(out int pfNoRedraw, uint dwFlags)
        {
            pfNoRedraw = 0;
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.OnInPlaceDeactivateEx(int fNoRedraw)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceSiteWindowless.RequestUIActivate()
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceSiteWindowless.CanWindowlessActivate()
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.GetCapture()
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.SetCapture(int fCapture)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.GetFocus()
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.SetFocus(int fFocus)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.GetDC(RECT[] pRect, uint grfFlags, out IntPtr phDC)
        {
            phDC = IntPtr.Zero;
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.ReleaseDC(IntPtr hDC)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceSiteWindowless.InvalidateRect(RECT[] pRect, int fErase)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.InvalidateRgn(IntPtr hRGN, int fErase)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.ScrollRect(int dx, int dy, RECT[] pRectScroll, RECT[] pRectClip)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.AdjustRect(RECT[] prc)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceSiteWindowless.OnDefWindowMessage(uint MSG, IntPtr wParam, IntPtr lParam, out int plResult)
        {
            plResult = 0;
            return HRESULT.S_OK;
        }

        #endregion

        #region IOleInPlaceFrame

        int IOleInPlaceFrame.GetWindow(ref IntPtr phwnd)
        {
            phwnd = Handle;

            if (phwnd == IntPtr.Zero)
                return HRESULT.E_FAIL;
            else
                return HRESULT.S_OK;
        }

        int IOleInPlaceFrame.ContextSensitiveHelp(bool fEnterMode)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceFrame.GetBorder(ref RECT lprectBorder)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceFrame.RequestBorderSpace(ref RECT pborderwidths)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceFrame.SetBorderSpace(ref RECT pborderwidths)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceFrame.SetActiveObject(ref IOleInPlaceActiveObject pActiveObject, string pszObjName)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceFrame.InsertMenus(IntPtr hmenuShared, OLEMENUGROUPWIDTHS[] lpMenuWidths)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceFrame.SetMenu(IntPtr hmenuShared, IntPtr HOLEMENU, IntPtr hwndActiveObject)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceFrame.RemoveMenus(IntPtr hmenuShared)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceFrame.SetStatusText(string pszStatusText)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceFrame.EnableModeless(int fEnable)
        {
            return HRESULT.S_OK;
        }

        int IOleInPlaceFrame.TranslateAccelerator(MSG[] lpmsg, ushort wID)
        {
            return HRESULT.E_NOTIMPL;
        }

        #endregion

        #region IOleInPlaceUIWindow

        int IOleInPlaceUIWindow.GetWindow(ref IntPtr phwnd)
        {
            phwnd = Handle;

            if (phwnd == IntPtr.Zero)
                return HRESULT.E_FAIL;
            else
                return HRESULT.S_OK;
        }

        int IOleInPlaceUIWindow.ContextSensitiveHelp(bool fEnterMode)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceUIWindow.GetBorder(ref RECT lprectBorder)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceUIWindow.RequestBorderSpace(ref RECT pborderwidths)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceUIWindow.SetBorderSpace(ref RECT pborderwidths)
        {
            return HRESULT.E_NOTIMPL;
        }

        int IOleInPlaceUIWindow.SetActiveObject(ref IOleInPlaceActiveObject pActiveObject, string pszObjName)
        {
            return HRESULT.S_OK;
        }

        #endregion
    }
}
